import { BaseService } from '../framework/base-service.js'
import _ from 'lodash'
import oracledb from 'oracledb'
import { platform } from 'os'

export default class RDBService extends BaseService {
  static metadata = {
    name: 'rdb',
    dependencies: [],
    singleton: true
  }
  
  constructor() {
    super()
    this.cache = new Map()
    this.adapter = null
  }
  
  async onInit() {
    this.dbType = this.config.get('database.type', 'oracle')
    this.proxyUrl = this.config.get('database.proxyUrl')
    this.useProxy = !!this.proxyUrl
    await this.loadAdapter()
    this.logger.info('RDBService', `✅ RDB service initialized (${this.dbType}, proxy: ${this.useProxy})`)
  }
  
  async loadAdapter() {
    if (this.useProxy) {
      this.adapter = new ProxyAdapter(this.proxyUrl, this.config)
    } else {
      const adapters = {
        'oracle': OracleAdapter,
        'mysql': MySQLAdapter,
        'postgres': PostgresAdapter
      }
      
      const AdapterClass = adapters[this.dbType]
      if (!AdapterClass) {
        console.warn(`Unsupported database type: ${this.dbType}, using proxy mode`)
        this.adapter = new ProxyAdapter(this.proxyUrl, this.config)
      } else {
        this.adapter = new AdapterClass(this.config)
      }
    }
  }
  
  async execute(sql, params = {}) {
    const finalSql = this.prepareSql(sql, params)
    
    const cacheKey = this.getCacheKey(finalSql, params)
    if (this.cache.has(cacheKey)) {
      const cached = this.cache.get(cacheKey)
      if (Date.now() - cached.time < 5000) {
        return cached.data
      }
    }
    
    try {
      const result = await this.adapter.execute(finalSql, params)
      
      this.cache.set(cacheKey, {
        data: result,
        time: Date.now()
      })
      
      if (this.cache.size > 100) {
        const firstKey = this.cache.keys().next().value
        this.cache.delete(firstKey)
      }
      
      return result
    } catch (err) {
      console.error('RDB query failed:', err)
      throw err
    }
  }
  
  prepareSql(sql, params) {
    const replacements = {
      ...params,
      '{gpsuser}': this.config.get('biz.oracle.connect.gpsuser', 'xzgps'),
      '{gpsname}': this.config.get('biz.oracle.connect.gpsuser', 'xzgps'),
      '{bizuser}': this.config.get('biz.oracle.connect.user', 'system'),
      '{bizname}': this.config.get('biz.oracle.connect.user', 'system')
    }
    
    return _(replacements).reduce((sql, value, key) => {
      return sql.replace(new RegExp(key, 'g'), value)
    }, sql)
  }
  
  getCacheKey(sql, params) {
    return `${sql}:${JSON.stringify(params)}`
  }
  
  registerRoutes(app) {
    app.post('/api/rdb/query', async c => {
      const { sql, params } = await c.req.json()
      
      try {
        const result = await this.execute(sql, params)
        return c.json({
          status: true,
          rows: result.rows || result,
          metaData: result.metaData
        })
      } catch (err) {
        return c.json({
          status: false,
          error: err.message
        }, 500)
      }
    })
    
    app.post('/api/biz/oracle', async c => {
      const { sql, params } = await c.req.json()
      
      try {
        const result = await this.execute(sql, params)
        return c.json({
          status: true,
          rows: result.rows || result,
          metaData: result.metaData
        })
      } catch (err) {
        return c.json({
          status: false,
          error: err.message
        }, 500)
      }
    })
    
    app.get('/api/rdb/status', c => {
      return c.json({
        type: this.dbType,
        mode: this.useProxy ? 'proxy' : 'direct',
        cacheSize: this.cache.size,
        adapter: this.adapter ? this.adapter.constructor.name : 'none'
      })
    })
    
    return true
  }
}

class ProxyAdapter {
  constructor(proxyUrl, config) {
    this.proxyUrl = proxyUrl || 'http://mapcan.xyz:18700/api/biz/oracle'
    this.config = config
  }
  
  async execute(sql, params) {
    const response = await fetch(this.proxyUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        sql,
        params,
        config: this.config.get('biz.oracle.connect')
      })
    })
    
    if (!response.ok) {
      throw new Error(`Proxy error: ${response.status} ${response.statusText}`)
    }
    
    const data = await response.json()
    
    if (data.status === false) {
      throw new Error(data.error || 'Query failed')
    }
    
    return {
      rows: data.rows || [],
      metaData: data.metaData || []
    }
  }
}

class OracleAdapter {
  constructor(config) {
    this.config = config
    this.connection = null
  }
  
  async execute(sql, params) {
    try {
      // 设置Oracle客户端参数
      oracledb.outFormat = oracledb.OBJECT
      oracledb.maxRows = this.config.get('database.oracle.maxRows') || 1000
      oracledb.autoCommit = this.config.get('database.oracle.autoCommit') !== false
      
      // 初始化Oracle客户端库
      const projectRoot = process.cwd()
      const os = platform()
      const libDir = projectRoot + '/OCI/11_2_' + 
        (os === 'linux' ? 'N' : os === 'darwin' ? 'M' : 'W')
      
      try {
        oracledb.initOracleClient({ libDir })
        this.logger.info(`Oracle客户端初始化成功: ${libDir}`)
      } catch (err) {
        this.logger.warn(`Oracle客户端初始化警告: ${err.message}`)
      }
      if (!this.connection) {
        const connectConfig = this.config.get('database.oracle.connect')
        this.connection = await oracledb.getConnection(connectConfig)
      }
      
      const result = await this.connection.execute(sql, params || {})
      
      const rows = result.rows || []
      const metaData = result.metaData || []
      
      if (metaData.length > 0 && rows.length > 0) {
        const formattedRows = _(rows).map(row => {
          return _(row).reduce((obj, value, index) => {
            const colName = metaData[index].name
            obj[colName] = value
            return obj
          }, {})
        }).value()
        
        return {
          rows: formattedRows,
          metaData
        }
      }
      
      return {
        rows,
        metaData
      }
    } catch (err) {
      console.error('Oracle adapter error:', err)
      throw err
    }
  }
  
  async close() {
    if (this.connection) {
      await this.connection.close()
      this.connection = null
    }
  }
}

class MySQLAdapter {
  constructor(config) {
    this.config = config
  }
  
  async execute(sql, params) {
    throw new Error('MySQL adapter not implemented yet')
  }
}

class PostgresAdapter {
  constructor(config) {
    this.config = config
  }
  
  async execute(sql, params) {
    throw new Error('Postgres adapter not implemented yet')
  }
}